/*
 * Copyright 2016 - 2020 Michael Rapp
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package de.mrapp.tabswitcher;

import java.lang.ref.SoftReference;

import de.mrapp.util.Condition;
import ohos.agp.components.Component;
import ohos.app.Context;
import ohos.utils.PacMap;
import ohos.utils.PlainArray;

public abstract class StatefulTabSwitcherDecorator<StateType> extends TabSwitcherDecorator {

    /**
     * A sparse array, which is used to store the states of tabs.
     */
    private PlainArray<SoftReference<StateType>> states;

    /**
     * The method, which is invoked on subclasses in order to create the state for a specific tab.
     * This method is invoked, when a tab is shown or refreshed and no corresponding state exists
     * yet.
     *
     * @param context            The context, the tab switcher belongs to, as an instance of the class {@link
     *                           Context}. The context may not be null
     * @param tabSwitcher        The tab switcher, whose tabs are visualized by the decorator, as an instance of the
     *                           type {@link TabSwitcher}. The tab switcher may not be null
     * @param view               The view, which is used to visualize the tab, as an instance of the class {@link
     *                           Component}. The view may not be null
     * @param tab                The tab, for which a state should be created, as an instance of the class {@link
     *                           Tab}. The tab may not be null
     * @param index              The index of the tab, for which a state should be created, as an {@link Integer}
     *                           value
     * @param viewType           The view type of the tab, for which a state should be created, as an {@link Integer}
     *                           value
     * @param savedInstanceState The bundle, which has previously been used to save the state of the tab as an
     *                           instance of the class {@link PacMap} or null, if no saved state is available
     * @return The state, which has been created, as an instance of the generic type {@link
     * StateType} or null, if no state has been created
     */
    protected abstract StateType onCreateState(final Context context,
                                               final TabSwitcher tabSwitcher,
                                               final Component view, final Tab tab,
                                               final int index, final int viewType,
                                               final PacMap savedInstanceState);

    /**
     * The method, which is invoked on subclasses, when a state is cleared using the {@link
     * #clearState(Tab)} or {@link #clearAllStates()} method.
     *
     * @param state The state, which is cleared, as an instance of the generic type {@link StateType}.
     *              The state may not be null
     */
    protected void onClearState(final StateType state) {

    }

    /**
     * The method which is invoked, when the view, which is used to visualize a tab, should be
     * shown, respectively when it should be refreshed. The purpose of this method is to customize
     * the appearance of the view, which is used to visualize the corresponding tab, depending on
     * its state and whether the tab switcher is currently shown, or not.
     *
     * @param context            The context, the tab switcher belongs to, as an instance of the class {@link
     *                           Context}. The context may not be null
     * @param tabSwitcher        The tab switcher, whose tabs are visualized by the decorator, as an instance of the
     *                           type {@link TabSwitcher}. The tab switcher may not be null
     * @param view               The view, which is used to visualize the tab, as an instance of the class {@link
     *                           Component}. The view may not be null
     * @param tab                The tab, which should be visualized, as an instance of the class {@link Tab}. The tab
     *                           may not be null
     * @param index              The index of the tab, which should be visualized, as an {@link Integer} value
     * @param viewType           The view type of the tab, which should be visualized, as an {@link Integer} value
     * @param state              The state of the tab, which should be visualized, as an instance of the generic type
     *                           {@link StateType} or null, if no state has been created yet
     * @param savedInstanceState The bundle, which has previously been used to save the state of the view as an
     *                           instance of the class {@link PacMap} or null, if no saved state is available
     * @see #onShowTab(Context, TabSwitcher, Component, Tab, int, int, PacMap)
     */
    protected abstract void onShowTab(final Context context,
                                      final TabSwitcher tabSwitcher,
                                      final Component view, final Tab tab,
                                      final int index, final int viewType,
                                      final StateType state,
                                      final PacMap savedInstanceState);

    /**
     * The method, which is invoked, when the view, which is used to visualize a tab, is removed.
     * The purpose of this method is to save the current state of the tab in a bundle.
     *
     * @param view     The view, which is used to visualize the tab, as an instance of the class {@link
     *                 Component}
     * @param tab      The tab, whose state should be saved, as an instance of the class {@link Tab}. The
     *                 tab may not be null
     * @param index    The index of the tab, whose state should be saved, as an {@link Integer} value
     * @param viewType The view type of the tab, whose state should be saved, as an {@link Integer} value
     * @param state    The state of tab, whose state should be saved, as an instance of the generic type
     *                 {@link StateType} or null, if no state has been created yet
     * @param outState The bundle, the state of the tab should be saved to, as an instance of the class
     *                 {@link PacMap}. The bundle may not be null
     * @see #onSaveInstanceState(Component, Tab, int, int, PacMap)
     */
    protected void onSaveInstanceState(final Component view, final Tab tab,
                                       final int index, final int viewType,
                                       final StateType state,
                                       final PacMap outState) {

    }

    /**
     * Returns the state of a specific tab.
     *
     * @param tab The tab, whose state should be returned, as an instance of the class {@link Tab}. The
     *            tab may not be null
     * @return The state of the given tab as an instance of the generic type {@link StateType} or
     * null, if no state has been created yet or if it was removed
     */

    public final StateType getState(final Tab tab) {
        Condition.INSTANCE.ensureNotNull(tab, "The tab may not be null");

        if (states != null) {
            SoftReference<StateType> reference = null;
            if (states.get(tab.hashCode()).isPresent()) {
                reference = states.get(tab.hashCode()).get();
            }

            if (reference != null) {
                return reference.get();
            }
        }

        return null;
    }

    /**
     * Removes the state of a specific tab.
     *
     * @param tab The tab, whose state should be removed, as an instance of the class {@link Tab}. The
     *            tab may not be null
     */
    public final void clearState(final Tab tab) {
        Condition.INSTANCE.ensureNotNull(tab, "The tab may not be null");

        if (states != null) {
            SoftReference<StateType> reference = null;
            if (states.get(tab.hashCode()).isPresent()) {
                reference = states.get(tab.hashCode()).get();
            }

            if (reference != null) {
                StateType state = reference.get();

                if (state != null) {
                    onClearState(state);
                }

                states.remove(tab.hashCode());

                if (states.size() == 0) {
                    states = null;
                }
            }
        }
    }

    /**
     * Removes the states of all tabs.
     */
    public final void clearAllStates() {
        if (states != null) {
            for (int i = 0; i < states.size(); i++) {
                SoftReference<StateType> reference = states.valueAt(i);
                StateType state = reference.get();

                if (state != null) {
                    onClearState(state);
                }
            }

            states.clear();
            states = null;
        }
    }

    @Override
    public final void onShowTab(final Context context,
                                final TabSwitcher tabSwitcher, final Component view,
                                final Tab tab, final int index, final int viewType,
                                final PacMap savedInstanceState) {
        if (states == null) {
            states = new PlainArray<>();
        }

        StateType state = getState(tab);

        if (state == null) {
            state = onCreateState(context, tabSwitcher, view, tab, index, viewType,
                    savedInstanceState);

            if (state != null) {
                states.put(tab.hashCode(), new SoftReference<>(state));
            }
        }

        onShowTab(context, tabSwitcher, view, tab, index, viewType, state, savedInstanceState);
    }

    @Override
    public final void onSaveInstanceState(final Component view, final Tab tab,
                                          final int index, final int viewType,
                                          final PacMap outState) {
        StateType state = getState(tab);
        onSaveInstanceState(view, tab, index, viewType, state, outState);
    }

}